<template>
  <div>
    <div id="pics">
      <span id="upload">
        <el-upload ref="upload"
                   :action="serverUrl+'/user/avatar'"
                   :auto-upload="false"
                   :headers="{Authorization: token}"
                   :multiple="false"
                   :on-change="choose"
                   :show-file-list="false"
                   accept="image/*">
          <el-button slot="trigger" icon="el-icon-upload2" type="success">上传本地图像</el-button>
        </el-upload>
      </span>

      <el-button :loading="isUploading" type="primary" @click="submit">确认</el-button>

      <div id="screen" ref="screen" @mousemove="moveImg"
           @mouseup="endMoving" @wheel.prevent="scrollResize" @mousedown.prevent="startMoving">
        <img ref="img" :src="imgSrc" :style="bigImgStyle" :width="w" alt="" @load="initImg">
        <div></div>
      </div>
      <span>预览</span>
      <canvas id="medium" ref="canvas" height="100" width="100"></canvas>
      <div id="small">
        <img :src="imgSrc" :style="smallImgStyle" :width="w/6" alt="">
      </div>
<!--      <canvas id="small" ref="canvasS" height="50" width="50"></canvas>-->
    </div>

    <div id="tools">
      <el-button :disabled="resize === 10" circle class="icon-btn" icon="el-icon-remove-outline"
                 size="mini" @click="resizeRateMinus"/>
      <el-slider v-model="resize" :max="20" :min="10" :show-tooltip="false" @input="resizeImg"/>
      <el-button :disabled="resize === 20" circle class="icon-btn" icon="el-icon-circle-plus-outline"
                 size="mini" @click="resizeRateAdd"/>
      <el-button circle class="icon-btn" icon="el-icon-refresh-left" size="mini" @click="antiClockWise"/>
      <el-button circle class="icon-btn" icon="el-icon-refresh-right" size="mini" @click="clockWise"/>
    </div>

  </div>
</template>

<script>
import {mapState} from "vuex";
export default {
  name: "SetAvatar",
  data() {
    return {
      resize: 10,
      imgSrc: '',
      rotateDeg: 0,
      wi: 0,
      hi: 0,
      sx: 0,
      sy: 0,
      sw: 0,
      sh: 0,
      isMoving: false,
      tick: null,
      tmpCanvas: new Map(),
      isUploading: false,
    }
  },
  computed: {
    ...mapState(['serverUrl', 'token']),
    resizeRate() {
      return this.resize / 10;
    },
    w() {
      return this.wi * this.resizeRate
    },
    h() {
      return this.hi * this.resizeRate
    },
    bigImgStyle() {
      return {
        left: -this.sx+'px',
        top: -this.sy+'px',
        transform: `rotateZ(${this.rotateDeg}turn)`
      }
    },
    smallImgStyle() {
      return {
        left: -this.sx/6+'px',
        top: -this.sy/6+'px',
        transform: `rotateZ(${this.rotateDeg}turn)`
      }
    },
  },
  methods: {
    choose(file) {
      if (!file) return
      if (!/^image/.test(file.raw.type) || file.raw.type === 'image/gif') {
        this.$alert('请选择静态图片类型的文件！','文件类型错误');
        this.$refs.upload.clearFiles();
      } else {
        this.imgSrc = URL.createObjectURL(file.raw);
      }
    },
    activateTick(delay) {
      // 触发节流定时器，对于鼠标移动和滚轮这种频繁触发的函数进行节流
      if (!this.tick) {
        this.tick = setTimeout(() => {
          this.tick = null;
        }, delay)
      }
    },

    resizeRateAdd() {
      if (this.resize < 20) this.resize += 1;
    },
    resizeRateMinus() {
      if (this.resize > 10) this.resize -= 1;
    },
    scrollResize(e) {
      if (!this.tick && this.imgSrc) {
        this.activateTick(50);
        if (e.deltaY < 0) this.resizeRateAdd();
        else this.resizeRateMinus();
      }
    },

    clockWise() {
      this.rotateDeg = this.rotateDeg === 0.5 ? -0.25 : this.rotateDeg + 0.25;
      this.draw();
    },
    antiClockWise() {
      this.rotateDeg = this.rotateDeg === -0.5 ? 0.25 : this.rotateDeg - 0.25;
      this.draw();
    },

    // 由于过度缩放（主要是缩小，因为头像框很小）会导致canvas绘制锯齿严重
    // 所以采用二分法逐步压缩，增加插值的饱和度
    // 为了避免在调整图像过程中重复生成中间画布，在加载图像后预先渲染好
    preRender() {
      this.tmpCanvas.clear();
      let w = this.sw;
      let h = this.sh;
      let img = this.$refs.img;
      while (Math.min(w, h) >= 400) {
        // 离屏canvas尚在开放阶段，未来可能有变化，保守做法可以改使用DOM生成
        w >>= 1; h >>= 1;
        const tmp = new OffscreenCanvas(w, h);
        tmp.getContext('2d').drawImage(img, 0, 0, w, h);
        img = tmp;
        this.tmpCanvas.set(w, img);
      }
    },
    initImg() {
      const img = this.$refs.img;
      this.sx = this.sy = 0;
      const nw = this.sw = this.wi = img.naturalWidth;
      const nh = this.sh = this.hi = img.naturalHeight;
      // img.width = nw;
      // img.height = nh;
      // 对于图片小于窗口尺寸的，先放大至能完全填充
      // 对于图片尺寸完全大于窗口的，先将短边缩小至适应窗口
      // 即将图片的短边缩放至窗口大小，长边按短边缩放比例缩放
      if (nw > nh) {
        this.wi = nw * 300 / nh;
        this.hi = 300;
      } else {
        this.hi = nh * 300 / nw;
        this.wi = 300;
      }

      this.preRender();
      this.rotateDeg = 0;
      if (this.resize !== 10) this.resize = 10;
      else this.draw();
      // else this.$nextTick(() => this.draw());
    },
    draw() {
      const ctx = this.$refs.canvas.getContext('2d');
      let img = this.$refs.img;
      ctx.clearRect(0, 0, 100, 100);
      ctx.restore();

      let w = this.sw, h = this.sh;
      const tw = this.w / 3, th = this.h / 3;
      if (this.tmpCanvas.size) {
        while (Math.min(w, h) >= 400 && w > (tw << 1)) {
          w >>= 1; h >>= 1;
        }
        img = this.tmpCanvas.get(w);
      }
      // sx和sy也需要根据图片缩放比例缩放，因为画布是按照原图来画的
      const rate = w / this.w;
      let [sx, sy] = [this.sx * rate, this.sy * rate];

      // 根据旋转角度情况，改变画布0点，和sx和sy轴偏移方向
      let x = 0, y = 0;
      switch (this.rotateDeg) {
        case 0.25: {
          x = 100;
          y = 0;
          [sx, sy] = [sy, (this.w - 300) * rate - sx];
          break;
        }
        case -0.25: {
          x = 0;
          y = 100;
          [sx, sy] = [(this.h - 300) * rate - sy, sx];
          break;
        }
        case 0.5 || -0.5: {
          x = y = 100;
          [sx, sy] = [(this.w - 300) * rate - sx, (this.h - 300) * rate - sy];
          break;
        }
      }
      ctx.save();
      ctx.translate(x, y);
      ctx.rotate(Math.PI * 2 * this.rotateDeg);

      ctx.drawImage(img, sx, sy, w, h, 0, 0, tw, th);
    },

    resizeImg() {
      if (this.imgSrc) this.draw();
    },
    startMoving() {
      this.isMoving = true;
    },
    endMoving() {
      this.isMoving = false;
    },
    moveImg(e) {
      if (this.isMoving && this.imgSrc && !this.tick) {
        // e.preventDefault();
        this.activateTick(20);
        const mx = e.movementX;
        const my = e.movementY;
        if (mx) {
          if (mx > 0) {
            this.sx = Math.max(0, this.sx - 10);
          } else {
            this.sx = Math.min(this.w - 300, this.sx + 10);
          }
        }
        if (my) {
          if (my > 0) {
            this.sy = Math.max(0, this.sy - 10);
          } else {
            this.sy = Math.min(this.h - 300, this.sy + 10);
          }
        }
        this.draw();
      }
    },

    submit() {
      this.$refs.canvas.toBlob((blob) => {
        this.isUploading = true;
        const formData = new FormData();
        formData.set('file', blob, 'avatar.png');
        this.$axios.post(this.serverUrl + '/user/avatar', formData, {
          headers: {Authorization: this.token}
        }).then(res => {
          this.isUploading = false;
          if (res.data.status === 0) {
            this.$message.success('修改头像成功！');
          } else {
            this.$alert(res.data.msg, '修改失败');
          }
        }).catch(err => {
          this.isUploading = false;
          this.$alert(err, '网络连接出错');
        })
      })
    },
  },
  watch:{
    resizeRate(cur, pre) {
      // 缩小时，左上角偏移距离也需要相应缩小，否则可能出现空白
      if (cur < pre) {
        const dw = this.wi * (pre - cur);
        const dh = this.hi * (pre - cur);
        this.sx = Math.max(0, this.sx - dw);
        this.sy = Math.max(0, this.sy - dh);
      }
    }
  },
}
</script>

<style lang="scss" scoped>

  #pics {
    display: grid;
    grid-template-columns: 300px 100px;
    grid-template-rows: 60px 50px 100px 150px;
    grid-template-areas: 'btn1 btn2' 'big tag' 'big med' 'big sma';
    align-items: center;
    justify-content: space-around;
    position: relative;

    #upload {
      grid-area: btn1;
    }
    .el-button {
      grid-area: btn2;
    }

    img {
      position: absolute;
    }

    #screen{
      grid-area: big;
      overflow: hidden;
      width: 100%;
      height: 100%;
      position: relative;
      cursor: grab;
      div {
        width: 100%;
        height: 100%;
        border-radius: 50%;
        box-shadow: 0 0 0 100px rgba(0,0,0,.5);
        position: absolute;
      }
    }

    span {
      grid-area: tag;
      text-align: center;
    }
    canvas {
      border-radius: 50%;
      border: 1px solid lightslategray;
    }
    #medium {
      grid-area: med;
    }
    #small {
      grid-area: sma;
      justify-self: center;
      border-radius: 50%;
      border: 1px solid lightslategray;
      overflow: hidden;
      position: relative;
      width: 50px;
      height: 50px;
    }
  }

  #tools {
    display: flex;
    gap: 4%;
    margin-top: 3%;

    .icon-btn {
      font-size: 28px !important;
      border: none;
      padding: 0;
    }

    .el-slider {
      width: 50%;
    }
  }

</style>